/*
 * measurement.c
 *
 *  Created on: Oct 28, 2019
 *      Author: Standa
 *
 *	This module provides fixed count (ADC_READSPERMEASPERIOD) of fast analog measurements of VM10R, VM100R and IM channels during single period (1/freq_Hz).
 *	Secondary function is to provide monitoring of VM10R, VM100R, IM, VBAT, VREF (for VDDA calculation) and TEMP when fast measurement is not active.
 *	TIM1 is the counter reserved for ADC conversion triggering and DMA1 CH1 used to transfer the converted data into memory.
 */

#include "measurement.h"
#include "error.h"
#include "sin128.h"
#include "adc.h"
#include "dma.h"
#include "tim.h"
#include "calibration.h"
#include "qfplib.h"


static uint8_t Initialized = 0, Prepared = 0, Running = 0, Sampling = 0, ContinueRequested = 0;
uint16_t CycleCounter = 0;
static float DesiredFreq_Hz = 0, ActualFreq_Hz = 0;
static uint16_t ADCBuffer[3*MEAS_READSPERMEASPERIOD];			//Readings in order VM100, IM, VM1000, VM100, ...
void (*CycleFinishedHook)(void) = 0;							//Pointer to function (provided by supervisor modules) which is called when each measurement cycle is finished (called from ISR), do not block

/*
 * Initialize generator module, used typically during boot only
 */
void meas_Init(void){
	LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_NONE);								//DMA transfer must be disabled before ADC calibration
	LL_ADC_StartCalibration(ADC1);
	while(LL_ADC_IsCalibrationOnGoing(ADC1));
	LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_LIMITED);							//Activate DMA ADC to memory transfer
	Initialized = 1;
}


/*
 * Prepares all the configurations and calculations (might be lengthy call).
 * Parameter freq_Hz defines the period across which the measurement is taken, startDelay_perc defines phase shift between start and first reading (from 25 up to 75 % of measurement period)
 */
err_Td meas_Prepare(float freq_Hz, float startDelay_perc){
	LL_RCC_ClocksTypeDef Clk;

	if(freq_Hz<MEAS_FMIN_HZ || freq_Hz>MEAS_FMAX_HZ) return err_Td_Range;
	if(startDelay_perc < 10 || startDelay_perc > 75) return err_Td_NotValid;

	LL_ADC_REG_SetSequencerChRem(ADC1, LL_ADC_CHANNEL_9);											//Dont convert VBAT, TEMP and VREF during standard fast sequential operation
	LL_ADC_REG_SetSequencerChRem(ADC1, LL_ADC_CHANNEL_TEMPSENSOR);
	LL_ADC_REG_SetSequencerChRem(ADC1, LL_ADC_CHANNEL_VREFINT);

	//Prepare ADC trigger timer TIM1
	LL_RCC_GetSystemClocksFreq(&Clk);
	for(int Presc=1; Presc<0xFFFF; Presc++){
		uint32_t PeriodCnt = qfp_float2uint( qfp_fmul( qfp_fdiv(1, qfp_fmul(freq_Hz, MEAS_READSPERMEASPERIOD)), qfp_fdiv(Clk.SYSCLK_Frequency, Presc) ) );	//Calculate how high the cnt register gets during single ReadingPerod (1 / (freq_Hz * ADC_READSPERMEASPERIOD))
		if( PeriodCnt <= UINT16_MAX ){																//If it is less or equal to maximum 16bit register value
			LL_TIM_SetCounter(TIM1, 0);																//Restart the counter
			LL_TIM_SetPrescaler(TIM1, Presc-1);														//Set actual prescaler
			LL_TIM_SetAutoReload(TIM1, PeriodCnt-1);												//Set counter period
			LL_TIM_ClearFlag_UPDATE(TIM16);															//Clear update flag which might be set during configuration
			ActualFreq_Hz = qfp_fdiv(Clk.SYSCLK_Frequency, qfp_fmul(Presc, qfp_fmul(PeriodCnt, MEAS_READSPERMEASPERIOD)));	//For high FM (above 1kHz) actual FM might differ from desired due to discrete steps in sampling interval
			DesiredFreq_Hz = freq_Hz;
			break;
		}
	}

	//Prepare the synchronization counter (ADC to DAC offset, synchronization of repeated starts)
	for(int Presc=1; Presc<0xFFFF; Presc++){
		uint32_t PeriodCnt = qfp_float2uint( qfp_fmul(qfp_fdiv(1, freq_Hz), qfp_fdiv(Clk.SYSCLK_Frequency, Presc)) );	//Calculate how high the cnt register gets during single ReadingPerod (1 / (freq_Hz * ADC_READSPERMEASPERIOD))
		if( PeriodCnt <= UINT16_MAX ){																//If it is less or equal to maximum 16bit register value
			LL_TIM_SetCounter(TIM17, 0);															//Restart the counter
			LL_TIM_SetPrescaler(TIM17, Presc - 1);
			LL_TIM_OC_SetCompareCH1(TIM17, (uint16_t)qfp_float2uint( qfp_fdiv( qfp_fmul( qfp_fmul(PeriodCnt, 0.01), startDelay_perc), MEAS_READSPERMEASPERIOD)));	//CC1 corresponds to startDelay_us parameter, used to actually start the sampling (either by meas_Start or meas_Continue)
			LL_TIM_SetAutoReload(TIM17, PeriodCnt - 1);												//Update corresponds to freq_Hz
			LL_TIM_ClearFlag_CC1(TIM17);
			LL_TIM_ClearFlag_UPDATE(TIM17);
			LL_TIM_EnableIT_CC1(TIM17);
			LL_TIM_EnableIT_UPDATE(TIM17);
			ActualFreq_Hz = qfp_fdiv(Clk.SYSCLK_Frequency, qfp_fmul(Presc, qfp_fmul(PeriodCnt, MEAS_READSPERMEASPERIOD)));	//For high FM (above 1kHz) actual FM might differ from desired due to discrete steps in sampling interval
			DesiredFreq_Hz = freq_Hz;
			break;
		}
	}

	LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
	LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1, (uint32_t)(&(ADC1->DR)), (uint32_t)(ADCBuffer), LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 3*MEAS_READSPERMEASPERIOD);
	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
	LL_ADC_Enable(ADC1);
	LL_ADC_REG_StartConversion(ADC1);

	LL_DMA_ClearFlag_TC1(DMA1);
	LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);

	Prepared = 1;
	return err_Td_Ok;
}


/*
 * Start the measurement if prepared beforehand (meas_Prepare)
 */
err_Td meas_Start(void){
	if(Running) return err_Td_Busy;

	Running = 1;
	Sampling = 1;
	ContinueRequested = 0;
	CycleCounter = 0;

	LL_TIM_EnableCounter(TIM17);														//Just delay between first DAC (DMA to GPIO) update and first ADC reading to allow reduce transient effects
	return err_Td_Ok;
}


/*
 * Continue the measurement if already started and first evaluation done (will not wait for startdelay and start, but instead synchronize with original meas_Start period)
 */
err_Td meas_Continue(void){
	if(!Running) return err_Td_NotValid;
	else if(Running && Sampling) return err_Td_Busy;

	Sampling = 1;
	ContinueRequested = 1;

	return err_Td_Ok;
}


/*
 * Stop the measurement
 */
void meas_Stop(void){
	LL_TIM_DisableCounter(TIM1);
	LL_TIM_DisableCounter(TIM17);

	LL_TIM_DisableIT_UPDATE(TIM17);
	LL_TIM_ClearFlag_UPDATE(TIM17);

	LL_TIM_DisableIT_CC1(TIM17);
	LL_TIM_DisableIT_UPDATE(TIM17);
	LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1);
	LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);	//CHECK

	LL_ADC_REG_StopConversion(ADC1);
	LL_ADC_Disable(ADC1);	//CHECK

	LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_9);										//VBAT, TEMP and VREF not measured during standard fast sequential operation, but during normal snapshot measurement it should be available
	LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_TEMPSENSOR);
	LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_VREFINT);

	Running = 0;
	Sampling = 0;
	Prepared = 0;
	ContinueRequested = 0;
	DesiredFreq_Hz = 0;
	ActualFreq_Hz = 0;
}


/*
 * Returns the state of generator (0-disabled, 1-active)
 */
uint8_t meas_GetState(void){
	return Running;
}


/*
 * Returns the pointer to data buffer with all the readings
 * If fast measurement is active format is VM100R_1st, IM_1st, VM10R_1st, VM100R_2nd, IM_2nd, VM10R_2nd, ...
 * If fast measurement is not active, format is VM100R, IM, VM10R, VBAT, Temp, VREF.
 */
uint16_t* meas_GetDataBuffer(void){
	return ADCBuffer;
}


void meas_SetCycleFinishedHook(void (*cycleFinishedHook)(void)){
	CycleFinishedHook = cycleFinishedHook;
}


/*
 * Only to be used by TIM17 ISR, handles CC1 and UPDATE interrupts
 */
void meas_SynchronizationHandler(void){
	if(LL_TIM_IsActiveFlag_CC1(TIM17)){
		LL_TIM_ClearFlag_CC1(TIM17);

		if( !LL_TIM_IsEnabledCounter(TIM1) ){
			//LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_9);
			LL_TIM_EnableCounter(TIM1);	//Start the main triggering counter if delay passed and it is not enabled yet (only during first pass)
			LL_TIM_GenerateEvent_UPDATE(TIM1);	//Trigger immediately as this is already the right time to do it (startDelay has just passed)
		}
	}

	if(LL_TIM_IsActiveFlag_UPDATE(TIM17)){
		LL_TIM_ClearFlag_UPDATE(TIM17);

		if(ContinueRequested){
			ContinueRequested = 0;

			LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
			LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1, (uint32_t)(&(ADC1->DR)), (uint32_t)(ADCBuffer), LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
			LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 3*MEAS_READSPERMEASPERIOD);
			LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
			LL_ADC_REG_StartConversion(ADC1);
			//LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_9);
		}
	}
}


/*
 * Gets called by DMA1 after last measurement is transferred via DMA into buffer.
 */
void meas_EndOfADCReadingHandler(void){
	LL_DMA_ClearFlag_TC1(DMA1);

	//LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_9);
	Sampling = 0;
	CycleCounter++;

	if(CycleFinishedHook) CycleFinishedHook();
}


static void ConvertAllChannelsSync(void){
	if(meas_GetState()) return;

	LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
	LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1, (uint32_t)(&(ADC1->DR)), (uint32_t)(ADCBuffer), LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 6);
	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
	LL_DMA_ClearFlag_TC1(DMA1);
	LL_ADC_REG_SetTriggerSource(ADC1, LL_ADC_REG_TRIG_SOFTWARE);
	LL_ADC_Enable(ADC1);
	LL_ADC_REG_StartConversion(ADC1);

	while(!LL_DMA_IsActiveFlag_TC1(DMA1));	//Wait until all the channels are converted and values transferred to buffer

	LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);	//CHECK
	LL_ADC_REG_StopConversion(ADC1);
	LL_ADC_Disable(ADC1);	//CHECK
	LL_ADC_REG_SetTriggerSource(ADC1, LL_ADC_REG_TRIG_EXT_TIM1_TRGO);
}


/*
 * Returns voltage measured on universal battery, button, buzzer I/O
 * Optionally retrieves also all the other converted values
 */
err_Td meas_ReadAnalogInputsSync_V(meas_InputTd *results){
	if(results == 0) return err_Td_Null;
	else if(meas_GetState()) return err_Td_Busy;

	ConvertAllChannelsSync();

	results->V100R_V = qfp_fmul(ADCBuffer[0], qfp_fdiv(CAL_VREF_25DEG_V, MEAS_ADCRANGE));
	results->IM_V = qfp_fmul(ADCBuffer[1], qfp_fdiv(CAL_VREF_25DEG_V, MEAS_ADCRANGE));
	results->V10R_V = qfp_fmul(ADCBuffer[2], qfp_fdiv(CAL_VREF_25DEG_V, MEAS_ADCRANGE));
	results->VBBB_V = qfp_fmul(ADCBuffer[3], qfp_fdiv(CAL_VREF_25DEG_V, MEAS_ADCRANGE));	//Fourth converted value in sequence is VBAT monitoring

	results->Vcc_V = qfp_fmul( qfp_fmul(0.001, VREFINT_CAL_VREF), qfp_fmul(*VREFINT_CAL_ADDR, ADCBuffer[5]) );


	//float TSlope = (float)(TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP) / (float)(*TEMPSENSOR_CAL2_ADDR - *TEMPSENSOR_CAL1_ADDR);
	//results->Temp_Deg = TEMPSENSOR_CAL1_TEMP + ( ADCBuffer[4]*results->Vcc_V/TEMPSENSOR_CAL_VREFANALOG - *TEMPSENSOR_CAL1_ADDR) * TSlope ;	//30+(V30+VSENSE)/AVGSLOPE;


	return err_Td_Ok;
}
